//Библиотека Qt.
#include <QTimer>
#include <QDirIterator>


//Внутренняя библиотека.
#include "filereader.h"
#include "vidstream.h"
#include "vidframe.h"


//===========================================================================//
//=Заголовок: FileReader::FileReader() - public
//=Параметры:
//=Описание: Конструктор.
//===========================================================================//
FileReader::FileReader(QString path, int index, QObject *parent) : VidDevice(index, parent)
{
moveToThread(this);

devState = UNACTIVE;
curFileIndex = 0; isRing = false;

fmtCtx = NULL; codecCtx = NULL; vidCodec = NULL;
srcVidFrame = NULL; dstVidFrame = NULL;
swsCtx = NULL;
vidStreamIndex = -1;

ffmpegPixFmt = PIX_FMT_YUV420P;
width = 512; height = 284;

vidFrame = NULL; vidTimer = NULL;

QStringList name_filters;
name_filters << "*.avi" << "*.mpg" << "*.mpeg" << "*.flv" << "*.swf" << "*.mov";

//Рекурсивно пройтись по файлам и каталогам указанного пути.
QDirIterator dir_i(path, name_filters, QDir::Files | QDir::NoSymLinks | QDir::NoDotAndDotDot, QDirIterator::Subdirectories);
while(dir_i.hasNext())
	{
	dir_i.next();
	filesList.append(dir_i.filePath());
}

//Установить базовое имя устройства.
QString basename = "Player";
if(!filesList.isEmpty())
	{
	QFileInfo finfo(filesList.first());
	basename = finfo.fileName();
}
VidDevice::setBaseName(basename);

//Инициализировать FFMPEG.
av_register_all();
}


//===========================================================================//
//=Заголовок: FileReader::~FileReader() - public
//=Параметры:
//=Описание: Деструктор.
//===========================================================================//
FileReader::~FileReader() {closeDevice();}


//===========================================================================//
//=Заголовок: bool FileReader::getListParam() - public
//=Параметры:
//=Описание: Функция возврата списка значений параметра устройства.
//===========================================================================//
bool FileReader::getListParam(DevParam::GET_LIST_PARAM /*param*/, QHash<int, QVariant> */*valhash*/)
{return true;}


//===========================================================================//
//=Заголовок: bool FileReader::getParam() - public
//=Параметры:
//=Описание: Функция возврата параметра устройства.
//===========================================================================//
bool FileReader::getParam(DevParam::GET_PARAM param, QVariant *value)
{
switch(param)
	{
	case DevParam::GET_FILENAME: {
		if(filesList.isEmpty()) return false;
		QFileInfo finfo(filesList.at(curFileIndex));
		(*value) = finfo.fileName();
	} break;
	case DevParam::GET_DEVNAME: (*value) = "Player"; break;
	case DevParam::GET_VIDEOWINDOW_CONNECTED: return VidDevice::getParam(param, value);
	default: return false;
}

return true;
}


//===========================================================================//
//=Заголовок: void FileReader::slotSetParam() - public slots
//=Параметры:
//=Описание: Слот установки параметра устройства.
//===========================================================================//
void FileReader::slotSetParam(DevParam::SET_PARAM /*param*/, int /*value*/) {}


//===========================================================================//
//=Заголовок: bool FileReader::openDevice() - protected
//=Параметры:
//=Описание: Функция открытия устройства.
//===========================================================================//
bool FileReader::openDevice()
{
if(devState != UNACTIVE) return false;
if(filesList.isEmpty()) return false;
if(filesList.size() <= curFileIndex) return false;

//Открыть видеофайл.
if(av_open_input_file(&fmtCtx, (const char*)filesList.at(curFileIndex).toAscii(), NULL, 0, NULL) != 0)
	{
	QFileInfo finfo(filesList.at(curFileIndex));
	QString msg = tr("запрос на открытие видеофайла ") + finfo.fileName() + tr(" не выполнен");
	emit critical(msg); return false;
}

//Получить информацию о потоке.
if(av_find_stream_info(fmtCtx) < 0)
	{
	QString msg = tr("запрос на получение информации о потоке не выполнен");
	emit critical(msg); closeDevice(); return false;
}

//Найти первый видеопоток.
vidStreamIndex = -1;
for(unsigned int i = 0; i < fmtCtx->nb_streams; ++i)
	{
	if(fmtCtx->streams[i]->codec->codec_type == CODEC_TYPE_VIDEO)
		{vidStreamIndex = i; break;}
}
if(vidStreamIndex == -1)
	{
	QString msg = tr("запрос на получение индекса видеопотока не выполнен");
	emit critical(msg); closeDevice(); return false;
}

//Получить информацию о видеокодеке.
codecCtx = fmtCtx->streams[vidStreamIndex]->codec;

//Найти видекодек.
vidCodec = avcodec_find_decoder(codecCtx->codec_id);
if(!vidCodec)
	{
	QString msg = tr("запрос на получение видеокодека не выполнен");
	emit critical(msg); closeDevice(); return false;
}

//Открыть видеокодек.
if(avcodec_open(codecCtx, vidCodec) < 0)
	{
	QString msg = tr("запрос на открытие видеокодека не выполнен");
	emit critical(msg); closeDevice(); return false;
}

//Исправить кадровый период, что неверно формируется некоторыми кодеками.
if(codecCtx->time_base.num > 1000 && codecCtx->time_base.den == 1)
	codecCtx->time_base.den = 1000;

//Создать видеобуфер-источник.
srcVidFrame = avcodec_alloc_frame();
if(!srcVidFrame)
	{
	QString msg = tr("запрос на создание видеобуфера-источника не выполнен");
	emit critical(msg); closeDevice(); return false;
}

//Установить базовое имя устройства.
QFileInfo finfo(filesList.at(curFileIndex));
VidDevice::setBaseName(finfo.fileName());

//Создать видеотаймер.
if(!vidTimer)
	{
	vidTimer = new QTimer(this);
	vidTimer->setInterval(40);
	connect(vidTimer, SIGNAL(timeout()), this, SLOT(slotVidTimerTimeout()), Qt::DirectConnection);
}

//Установить состояние устройства.
devState = ACTIVE;

QString msg = tr("выполнен запрос на открытие видеофайла ") + finfo.fileName();
emit information(msg);

return true;
}


//===========================================================================//
//=Заголовок: void FileReader::closeDevice() - protected
//=Параметры:
//=Описание: Функция закрытия устройства.
//===========================================================================//
void FileReader::closeDevice()
{
//Остановить воспроизведение видеофайла.
if(devState == CAPTURING) stopCapturing();

//Установить состояние устройства.
devState = UNACTIVE;

//Очистить видеобуфер.
if(srcVidFrame) av_free(srcVidFrame); srcVidFrame = NULL;

//Закрыть видеокодек.
if(codecCtx) avcodec_close(codecCtx); codecCtx = NULL;

//Закрыть файл устройства.
if(fmtCtx) av_close_input_file(fmtCtx); fmtCtx = NULL;

QString msg = tr("выполнен запрос на закрытие видеофайла");
emit information(msg);
}


//===========================================================================//
//=Заголовок: bool FileReader::startCapturing() - protected
//=Параметры:
//=Описание: Функция запуска видеозахвата.
//===========================================================================//
bool FileReader::startCapturing()
{
//Создать видеобуфер-назначение.
dstVidFrame = avcodec_alloc_frame();
if(!dstVidFrame)
	{
	QString msg = tr("запрос на создание видеобуфера-назначения не выполнен");
	emit critical(msg); closeDevice(); return false;
}

//Сохранить видеоразрешение.
width = codecCtx->width;
height = codecCtx->height;

//Создать буфер видеокадра.
vidFrame = new VidFrame();
size_t length = avpicture_get_size(ffmpegPixFmt, width, height);
uchar *vdata = new uchar[length];
vidFrame->setBuffer(vdata, length);
vidFrame->setFFMPEGPixFormat(ffmpegPixFmt);
vidFrame->setResolution(QSize(width, height));

//Объединить видеобуфер-назначение и буфер видеокадра.
avpicture_fill((AVPicture *)dstVidFrame, vidFrame->getData(), ffmpegPixFmt, width, height);

//Создать видеоконвертер.
swsCtx = sws_getCachedContext(swsCtx, width, height, codecCtx->pix_fmt, width, height, ffmpegPixFmt, SWS_BICUBIC, NULL, NULL, NULL);
if(!swsCtx)
	{
	QString msg = tr("запрос на создание видеоконвертера не выполнен");
	emit critical(msg); closeDevice(); return false;
}

//Активировать видеотаймер.
vidTimer->start();

//Установить состояние устройства.
devState = CAPTURING;

QString msg = tr("выполнен запрос на активацию устройства");
emit information(msg);

return true;
}


//===========================================================================//
//=Заголовок: bool FileReader::stopCapturing() - protected
//=Параметры:
//=Описание: Функция остановки видеозахвата.
//===========================================================================//
bool FileReader::stopCapturing()
{
//Установить состояние устройства.
devState = ACTIVE;

//Остановить видеотаймер.
if(vidTimer) vidTimer->stop();

//Удалить видеоконвертер.
if(swsCtx) sws_freeContext(swsCtx); swsCtx = NULL;

//Очистить видеокадр.
if(vidFrame)
	{
	uchar *vdata = vidFrame->getData();
	if(vdata) delete vdata;
	delete vidFrame;
}
vidFrame = NULL;

//Очистить видеобуфер.
if(dstVidFrame) av_free(dstVidFrame);

QString msg = tr("выполнен запрос на остановку устройства");
emit information(msg);

return true;
}


//===========================================================================//
//=Заголовок: void FileReader::slotVidTimerTimeout() - private slots
//=Параметры:
//=Описание: Слот сработки видеотаймера.
//===========================================================================//
void FileReader::slotVidTimerTimeout()
{
AVPacket packet;
if(av_read_frame(fmtCtx, &packet) < 0)
	{
	closeDevice();
	curFileIndex++;
	if(curFileIndex == filesList.size())
		{curFileIndex = 0; if(!isRing) return;}
	openDevice();
	startCapturing();
	emit nextFile(curFileIndex);
	return;
}

if(packet.stream_index == vidStreamIndex)
	{
	int framefinished;
	avcodec_decode_video(codecCtx, srcVidFrame, &framefinished, packet.data, packet.size);
	if(framefinished)
		{
		sws_scale(swsCtx, srcVidFrame->data, srcVidFrame->linesize, 0, codecCtx->height, dstVidFrame->data, dstVidFrame->linesize);
		vidStream->sendFrame(vidFrame);
	}
}

av_free_packet(&packet);
}
